package com.zhaopin.qa.jmeter.plugins.jsonpathbatchassertion;

import java.io.Serializable;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.jmeter.assertions.Assertion;
import org.apache.jmeter.assertions.AssertionResult;
import org.apache.jmeter.config.Arguments;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.testelement.AbstractTestElement;
import org.apache.jmeter.testelement.property.PropertyIterator;
import org.apache.jmeter.testelement.property.TestElementProperty;
import org.apache.jmeter.util.JMeterUtils;
import org.apache.oro.text.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.Option;

import net.minidev.json.JSONArray;
import net.minidev.json.JSONObject;

public class JSONPathBatchAssertion extends AbstractTestElement implements Serializable, Assertion {
	private static final Logger log = LoggerFactory.getLogger(JSONPathBatchAssertion.class);
	private static final long serialVersionUID = 1L;

	private static final String JSONPath_Assertion_Arguments = "JSONPathBatchAssertion.JSONPath_Assertion_Arguments"; //$NON-NLS-1$

	private static ThreadLocal<DecimalFormat> decimalFormatter = ThreadLocal
			.withInitial(JSONPathBatchAssertion::createDecimalFormat);

	private static DecimalFormat createDecimalFormat() {
		DecimalFormat decimalFormatter = new DecimalFormat("#.#");
		decimalFormatter.setMaximumFractionDigits(340); // java.text.DecimalFormat.DOUBLE_FRACTION_DIGITS == 340
		decimalFormatter.setMinimumFractionDigits(1);
		return decimalFormatter;
	}

	// 取路径 取值
	public static final Configuration JSONPATH_CONFIG = Configuration.builder().options(Option.AS_PATH_LIST,
			Option.ALWAYS_RETURN_LIST, Option.SUPPRESS_EXCEPTIONS, Option.DEFAULT_PATH_LEAF_TO_NULL).build();

	public void setJSONPathAssertionArguments(Arguments vars) {
		this.setProperty(new TestElementProperty(JSONPath_Assertion_Arguments, vars));
	}

	public Arguments getJSONPathAssertionArguments() {
		Arguments args = (Arguments) this.getProperty(JSONPath_Assertion_Arguments).getObjectValue();
		if (args == null) {
			args = new Arguments();
			this.setJSONPathAssertionArguments(args);
		}
		return args;
	}

	public List<JSONPathAssertionArgument> getJSONPathAssertionArgumentsAsList() {
		List<JSONPathAssertionArgument> JsonPathArgumentList = new ArrayList<>();
		Arguments args = this.getJSONPathAssertionArguments();
		PropertyIterator iterator = args.getArguments().iterator();
		while (iterator.hasNext()) {
			JSONPathAssertionArgument jsonPathAssertionArgument = (JSONPathAssertionArgument) iterator.next()
					.getObjectValue();
			JsonPathArgumentList.add(jsonPathAssertionArgument);
		}
		return JsonPathArgumentList;
	}

	private static boolean isEquals(JSONPathAssertionArgument jsonPathAssertionArgument, Object subj) {
		String str = objectToString(subj);
		if (jsonPathAssertionArgument.isUseRegex()) {
			Pattern pattern = JMeterUtils.getPatternCache().getPattern(jsonPathAssertionArgument.getExpectedValue());
			return JMeterUtils.getMatcher().matches(str, pattern);
		} else {
			return str.equals(jsonPathAssertionArgument.getExpectedValue());
		}
	}

	private void doAssert(JSONPathAssertionArgument jsonPathAssertionArgument, String jsonString) {
		Object value = JsonPath.read(jsonString, jsonPathAssertionArgument.getJsonPath());

		if (jsonPathAssertionArgument.isJsonValidationBool()) {
			if (value instanceof JSONArray) {
				if (arrayMatched(jsonPathAssertionArgument, (JSONArray) value)) {
					return;
				}
			} else {
				if (jsonPathAssertionArgument.isExpectNull() && value == null) {
					return;
				} else if (JSONPathBatchAssertion.isEquals(jsonPathAssertionArgument, value)) {
					return;
				}
			}

			if (jsonPathAssertionArgument.isExpectNull()) {
				throw new IllegalStateException(String.format("Value expected to be null, but found '%s'", value));
			} else {
				String msg;
				if (jsonPathAssertionArgument.isUseRegex()) {
					msg = "Value expected to match regexp '%s', but it did not match: '%s'";
				} else {
					msg = "Value expected to be '%s', but found '%s'";
				}
				throw new IllegalStateException(
						String.format(msg, jsonPathAssertionArgument.getExpectedValue(), objectToString(value)));
			}
		}
	}

	private static boolean arrayMatched(JSONPathAssertionArgument jsonPathAssertionArgument, JSONArray value) {
		if (value.isEmpty() && "[]".equals(jsonPathAssertionArgument.getExpectedValue())) {
			return true;
		}

		for (Object subj : value.toArray()) {
			if ((subj == null && jsonPathAssertionArgument.isExpectNull())
					|| isEquals(jsonPathAssertionArgument, subj)) {
				return true;
			}
		}

		return isEquals(jsonPathAssertionArgument, value);
	}

	@Override
	public AssertionResult getResult(SampleResult samplerResult) {
		AssertionResult result = new AssertionResult(this.getName());
		String responseData = samplerResult.getResponseDataAsString();
		if (responseData.isEmpty()) {
			return result.setResultForNull();
		}

		result.setFailure(false);
		result.setFailureMessage("");

		StringBuffer sb = new StringBuffer();

		this.getJSONPathAssertionArgumentsAsList().forEach(assertion -> {
			if (!assertion.isInvert()) {
				try {
					this.doAssert(assertion, responseData);
				} catch (Exception e) {
					if (log.isDebugEnabled()) {
						log.debug("Assertion failed", e);
					}
					result.setFailure(true);
					if (sb.length() > 0) {
						sb.append("\n");
					}
					sb.append(assertion.getJsonPath() + ": " + e.getMessage());
				}
			} else {
				try {
					this.doAssert(assertion, responseData);
					result.setFailure(true);
					if (sb.length() > 0) {
						sb.append("\n");
					}
					if (assertion.isJsonValidationBool()) {
						if (assertion.isExpectNull()) {
							sb.append(assertion.getJsonPath() + " not matches null");
						} else {
							sb.append(assertion.getJsonPath() + " not matches " + assertion.getExpectedValue());
						}
					} else {
						sb.append(assertion.getJsonPath() + " JSONPath not exists: ");
					}
				} catch (Exception e) {
					if (log.isDebugEnabled()) {
						log.debug("Assertion failed", e);
					}
				}
			}
		});
		result.setFailureMessage(sb.toString());
		return result;
	}

	public static String objectToString(Object subj) {
		String str;
		if (subj == null) {
			str = "null";
		} else if (subj instanceof Map) {
			// noinspection unchecked
			str = new JSONObject((Map<String, ?>) subj).toJSONString();
		} else if (subj instanceof Double || subj instanceof Float) {
			str = decimalFormatter.get().format(subj);
		} else {
			str = subj.toString();
		}
		return str;
	}
}